// The sample model.  You should build a file
// very similar to this for when you make your model.
#include "modelerview.h"
#include "modelerapp.h"
#include "modelerdraw.h"
#include "vec.h"
#include <FL/gl.h>
#include <vector>

#include "modelerglobals.h"

#include "marching.h"

// To make a SampleModel, we inherit off of ModelerView
class TestModel : public ModelerView 
{
public:
    TestModel(int x, int y, int w, int h, char *label);

    virtual void draw();

	void drawTorso();
	void drawEar();
	void drawHead();
	void drawTail();
	void drawEye();
	void drawArm();
	void drawFoot();
	void drawWedge(double width, double height, double length);

	void drawTailSegment(double scale_ratio, Vec3f* high, Vec3f* low);

	void drawTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings);
	void drawTorusRing(double innerRadius, double outerRadius, int nsides, double anglePerRing);

	// Variables
	Marching torso_shape;
	Marching metaballz;

	bool tailMoveDirection_1, tailMoveDirection_2, tailMoveDirection_3, tailMoveDirection_4;

};

// We need to make a creator function, mostly because of
// nasty API stuff that we'd rather stay away from.
ModelerView* createSampleModel(int x, int y, int w, int h, char *label)
{ 
    return new TestModel(x,y,w,h,label); 
}

TestModel::TestModel(int x, int y, int w, int h, char *label)
        : ModelerView(x,y,w,h,label), torso_shape(100, 100, 100), metaballz(100, 100, 100)
{ 

	torso_shape.AddMetaball(0.0, -1.3, 0.0, -0.25);
	torso_shape.AddMetaball(0.0, 1.5, 0.0, -1);
	torso_shape.AddMetaball(0.0, 0.0, 0.0);
	torso_shape.SetMetaThreshold(0.5);
	torso_shape.Prepare();

	metaballz.AddMetaball(0.0, 0.0, 0.0, 0.5);
	metaballz.AddMetaball(0.0, -1.0, 0.0, 0.5); // negative
	metaballz.SetMetaThreshold(2.5);
	metaballz.Prepare();

}

// We are going to override (is that the right word?) the draw()
// method of ModelerView to draw out SampleModel
void TestModel::draw()
{
    // This call takes care of a lot of the nasty projection 
    // matrix stuff.  Unless you want to fudge directly with the 
	// projection matrix, don't bother with this ...
    ModelerView::draw();

	// draw the floor

	// draw the sample model
	setAmbientColor(.1f,.1f,.1f); // "ambient light"
	setDiffuseColor(COLOR_YELLOW);
	
	// transform entire body
	glTranslated(VAL(XPOS), VAL(YPOS), VAL(ZPOS));
	
	glPushMatrix();


		// draw torso
		glPushMatrix();
		drawTorso();
		glPopMatrix();

		// draw face
		glPushMatrix();
		glTranslated(0.0, 1.15, 0.0);
		drawHead();
		glPopMatrix();

		// draw tail
		glPushMatrix();
		glTranslated(0.0, 0.0, -0.5);
		drawTail();
		glPopMatrix();
	
	glPopMatrix();
	
	// Draw torus just for demonstration purposes...
	drawTorus(5, 10, 50, 100);

	glPushMatrix();
	glTranslated(0, 0, -6);
	metaballz.SetMetaThreshold(VAL(META_THRESHOLD));
	metaballz.SetBallPower(0, VAL(POWER1));
	metaballz.SetBallPosition(0, Vec3f(0, VAL(BALL_HEIGHT), 0));
	metaballz.Prepare();
	metaballz.Draw();
	glPopMatrix();
	
	if (ModelerApplication::Instance()->IsAnimating()) {

		if (VAL(TAIL_ROTATE_1) > 120 || VAL(TAIL_ROTATE_1) < 60) tailMoveDirection_1 = !tailMoveDirection_1;
		if (VAL(TAIL_ROTATE_2) > 120 || VAL(TAIL_ROTATE_2) < 60) tailMoveDirection_2 = !tailMoveDirection_2;
		if (VAL(TAIL_ROTATE_3) > 120 || VAL(TAIL_ROTATE_3) < 60) tailMoveDirection_3 = !tailMoveDirection_3;
		if (VAL(TAIL_ROTATE_4) > 120 || VAL(TAIL_ROTATE_4) < 60) tailMoveDirection_4 = !tailMoveDirection_4;

		if (tailMoveDirection_1)
			ModelerApplication::Instance()->SetControlValue(TAIL_ROTATE_1, VAL(TAIL_ROTATE_1) + 1);
		else
			ModelerApplication::Instance()->SetControlValue(TAIL_ROTATE_1, VAL(TAIL_ROTATE_1) - 1);

		if (tailMoveDirection_2)
			ModelerApplication::Instance()->SetControlValue(TAIL_ROTATE_2, VAL(TAIL_ROTATE_2) + 1);
		else
			ModelerApplication::Instance()->SetControlValue(TAIL_ROTATE_2, VAL(TAIL_ROTATE_2) - 1);

		if (tailMoveDirection_3)
			ModelerApplication::Instance()->SetControlValue(TAIL_ROTATE_3, VAL(TAIL_ROTATE_3) + 1);
		else
			ModelerApplication::Instance()->SetControlValue(TAIL_ROTATE_3, VAL(TAIL_ROTATE_3) - 1);

		if (tailMoveDirection_4)
			ModelerApplication::Instance()->SetControlValue(TAIL_ROTATE_4, VAL(TAIL_ROTATE_4) + 1);
		else
			ModelerApplication::Instance()->SetControlValue(TAIL_ROTATE_4, VAL(TAIL_ROTATE_4) - 1);
	}
}

void TestModel::drawTorso() {
	setDiffuseColor(COLOR_YELLOW);

	// draw sphere main torso
	glPushMatrix();
	glScaled(1.0, 1.25, 1.0);
	// drawSphere(1);
	torso_shape.Draw();
	glPopMatrix();

	// left arm (right from front view)
	glPushMatrix();	
	glTranslated(1.0, 0.3, 0.5);
	glRotated(-VAL(CLAP) * 50.0, 0, 1, 0);
	drawArm();
	glPopMatrix();

	// right arm (left from front view)
	glPushMatrix();	
	glScaled(-1.0, 1, 1);
	glTranslated(1.0, 0.3, 0.5);
	glRotated(-VAL(CLAP) * 50.0, 0, 1, 0);
	drawArm();
	glPopMatrix();

	// left foot (right from the front view)
	glPushMatrix();
	glTranslated(0.25, -1.0, 0);
	glScaled(2.5, 5, 5);
	drawFoot();
	glPopMatrix();


	// right foot (left from the front view)
	glPushMatrix();
	glTranslated(-0.25, -1.0, 0);
	glScaled(2.5, 5, 5);
	drawFoot();
	glPopMatrix();

	// glPopMatrix();

}

// Draws head with centered 
void TestModel::drawHead() {

	// draw head
	glPushMatrix();
			
		// Draw face
		glPushMatrix();
		glScaled(0.85, 1.0, 0.85);
		drawSphere(1);
		glPopMatrix();

		// Draw eyes
		glPushMatrix();
		glRotated(-35, 1, 0, 0);
		glRotated(20, 0, 1, 0);
		glTranslated(0, 0, 0.73);
		glScaled(0.3 * 3.0/4.0, 0.3*3.0/4.0, 0.3 * 3.0/4.0);
		
		drawEye();

		glPopMatrix();

		// Draw eyes
		glPushMatrix();
		glRotated(-35, 1, 0, 0);
		glRotated(-20, 0, 1, 0);
		glTranslated(0, 0, 0.73);
		glScaled(0.3 * 3.0/4.0, 0.3*3.0/4.0, 0.3 * 3.0/4.0);
		
		drawEye();

		glPopMatrix();

		// Draw cheeks with spheres
		glPushMatrix();
		setDiffuseColor(COLOR_RED);
		glTranslated(0.6 * sin(M_PI/4.5), 0, 0.6 * cos(M_PI/4.5));

		drawSphere(0.3);
		glPopMatrix();

		glPushMatrix();
		setDiffuseColor(COLOR_RED);
		glTranslated(-0.6 * sin(M_PI/4.5), 0, 0.6 * cos(M_PI/4.5));

		drawSphere(0.3);
		glPopMatrix();

		// draw left ear
		glPushMatrix();
		glTranslated(-0.05 + 0.25, 0.85, -0.05); // move ear to the left
		glRotated(-VAL(CLAP) * 30.0, 0, 0, 1);
		drawEar();
		glPopMatrix();

		// draw right ear
		glPushMatrix();
		glTranslated(-0.05 - 0.25, 0.85, -0.05);
		glRotated(VAL(CLAP) * 30.0, 0, 0, 1);
		drawEar();
		glPopMatrix();

	glPopMatrix();

}

// Draws ear with the base at y = 0, centered at z = 0, x = 0
// Default height is 1, cylinder base from 0.2 to 0.10
void TestModel::drawEar() {

	glPushMatrix();

	setDiffuseColor(COLOR_YELLOW);

	// turn cylider upright
	glRotated(-90, 1, 0, 0);
	drawCylinder(1, 0.2, 0.15);

	glPopMatrix(); 

	////////////////////////////////

	glPushMatrix();

	setDiffuseColor(COLOR_BROWN);

	// draw top of the ear
	glTranslated(0.0, 1.0, 0.0);
	glScaled(1.0, 2.0, 1.0);
	drawSphere(0.15);
	glPopMatrix();

}

// Draw Tail with the base at the origin. By default, the tail is pointing in the NEGATIVE z direction
void TestModel::drawTail() {

	double ratio = 1;
	double factor = 1.25;

	glPushMatrix();

	Vec3f low, high;

	// segment 1
	setDiffuseColor(COLOR_BROWN);
	glRotated(VAL(TAIL_ROTATE_1) - 90, 1, 0, 0);
	drawTailSegment(2, &high, &low);

	// segment 2
	ratio = ratio * factor;
	setDiffuseColor(COLOR_YELLOW);
	glTranslated(low[0], low[1], low[2]);
	glRotated(VAL(TAIL_ROTATE_2), 1, 0, 0);
	drawTailSegment(ratio, &high, &low);

	// segment 3
	ratio = ratio * factor;
	setDiffuseColor(COLOR_YELLOW);
	glTranslated(high[0], high[1], high[2]);
	glRotated(-VAL(TAIL_ROTATE_3), 1, 0, 0);
	drawTailSegment(ratio, &high, &low);

	// segment 4
	ratio = ratio * factor;
	setDiffuseColor(COLOR_YELLOW);
	glTranslated(low[0], low[1], low[2]);
	glRotated(VAL(TAIL_ROTATE_4), 1, 0, 0);
	drawTailSegment(ratio, &high, &low);

	glPopMatrix();
}

// Draw a tail segment, with length 1, and span 0.125 (width = 0.25)
void TestModel::drawTailSegment(double scale_ratio, Vec3f* high, Vec3f* low) {

	glPushMatrix();

	glScaled(scale_ratio, scale_ratio, scale_ratio);
	drawTriangle(0, 0, 0, 0, 0.125, -1, 0, -0.125, -1);

	glPopMatrix();

	if (high != NULL) {
		(*high)[2] = -1.0 * scale_ratio;
		(*high)[1] = 0.125 * scale_ratio;
		(*high)[0] = 0.0 * scale_ratio;
	}

	if (low != NULL) {
		(*low)[2] = -1.0 * scale_ratio;
		(*low)[1] = -0.125 * scale_ratio;
		(*low)[0] = 0.0 * scale_ratio;
	}

}

// Draw arm with "shoulder" at 0,0,0, and pointing in the direction 1, 0, 0;
void TestModel::drawArm() {

	glPushMatrix();	

	glTranslated(0.1, 0, 0);
	glScaled(2, 1, 1);
	drawSphere(0.2);

	glPopMatrix();

}

// Draws a foot with the heel at 0, 0, 0, pointing in the direction 0, 0, 1
void TestModel::drawFoot() {
	glPushMatrix();
	drawWedge(0.1, 0.02, 0.2);
	glPopMatrix();
}

void TestModel::drawWedge(double width, double height, double length) {

	double w = width/2;

	glPushMatrix();

	// left and right triangles
	drawTriangle(-w, 0, 0, -w, -height, length, -w, -height, 0);
	drawTriangle(w, 0, 0, w, -height, length, w, -height, 0);

	// back heel
	drawTriangle(-w, 0, 0, w, 0, 0, w, -height, 0);
	drawTriangle(-w, 0, 0, w, -height, 0, -w, -height, 0);

	// top slope down
	drawTriangle(-w, 0, 0, -w, -height, length, w, 0, 0);
	drawTriangle(w, 0, 0, -w, -height, length, w, -height, length);

	// bottom face
	drawTriangle(-w, -height, 0, w, -height, 0, w, -height, length);
	drawTriangle(w, -height, length, -w, -height, length, -w, -height, 0);

	glPopMatrix();
}

// Draws eye with radius 1 
// Eye points in the POSITIVE z direction
void TestModel::drawEye() {

	glPushMatrix();
	
	setDiffuseColor(COLOR_BLACK);
	drawSphere(1.0f);

	glTranslated(0.0f, 0.0f, 0.35);

	setDiffuseColor(COLOR_WHITE);
	drawSphere(0.7f);

	glPopMatrix();

}

// Draws a torus with innerradius and outerradius, with the z axis poking throuh the center of the rings.
void TestModel::drawTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings) {

	// We assume that outerradius >= innerradius for things to make sense

	glPushMatrix();
	
	for (int x = 0; x < rings; x++) {
		glRotated(360.0/(double)rings, 0, 0, 1);
		drawTorusRing(innerRadius, outerRadius, nsides, (2 * M_PI)/(double)rings);
	}

	glPopMatrix();
	
}

// Draw one ring of the torus (with radius = innerRadius)
void TestModel::drawTorusRing(double innerRadius, double outerRadius, int nsides, double anglePerRing) {

	glPushMatrix();

	double anglePerSide = (2 * M_PI) / nsides;
	Vec3f c1 = Vec3f(outerRadius, 0, 0); // straight

	// balance out - not really necessary in the grand scheme of things
	glRotated(anglePerRing * 180 / M_PI / 2.0, 0, 0, 1);

	glBegin(GL_QUAD_STRIP);
	for (int n = 0; n <= nsides; n++) {
		double nx = outerRadius + innerRadius * cos((double)n * anglePerSide);
		double nz = innerRadius * sin((double)n * anglePerSide);
		glVertex3d(nx, 0, nz);
		// glVertex3d(nx, ny_const, nz);
		glVertex3d(nx*cos(anglePerRing), nx * sin(anglePerRing), nz);
	}

	glEnd();

	glPopMatrix();

}

int main()
{
	// Initialize the controls
	// Constructor is ModelerControl(name, minimumvalue, maximumvalue, 
	// stepsize, defaultvalue)
    ModelerControl controls[NUMCONTROLS];
    controls[XPOS] = ModelerControl("X Position", -5, 5, 0.1f, 0);
    controls[YPOS] = ModelerControl("Y Position", 0, 5, 0.1f, 0);
    controls[ZPOS] = ModelerControl("Z Position", -5, 5, 0.1f, 0);
	controls[TAIL_ROTATE_1] = ModelerControl("Tail_Angle_1", 60, 120, 1, 90);
	controls[TAIL_ROTATE_2] = ModelerControl("Tail_Angle_2", 60, 120, 1, 90);
	controls[TAIL_ROTATE_3] = ModelerControl("Tail_Angle_3", 60, 120, 1, 90);
	controls[TAIL_ROTATE_4] = ModelerControl("Tail_Angle_4", 60, 120, 1, 90);
	controls[CLAP] = ModelerControl("CLAP", 0, 1, 0.01, 0);
	controls[META_THRESHOLD] = ModelerControl("BALLS META THRESHOLD", 0, 4, 0.01, 2.5);
	controls[POWER1] = ModelerControl("BALL_POWER 1", -2, 2, 0.01, 0.5);
	controls[BALL_HEIGHT] = ModelerControl("BALL_HEIGHT1", -1, 1, 0.01, 0.0);

    ModelerApplication::Instance()->Init(&createSampleModel, controls, NUMCONTROLS);
    return ModelerApplication::Instance()->Run();
}
